using System;

namespace Neural
{
	
	#region interfaces

	/// <summary>
	/// Base interface for all feed-forward networks
	/// </summary>
	public interface INetwork
	{
		double Error { get; }
		double LearningRate { get; set; }
		double Momentum { get; set; }
		IActivation Activation { get; set; }
		double[] Inputs { get; }
		int InputCount { get; }
		double GetInput(int index);
		void SetInput(int index, double value);
		int OutputCount { get; }
		double[] Targets { get; }
		double GetTarget(int index);
		void SetTarget(int index, double value);
		double[] Outputs { get; }
		double GetOutput(int index);
		int GetBest();
		void Run();
		void Run(double[] inputs);
		void Randomize();		
		double Output(int index);
		double Output();
		double Output(double[] inputs, int index);
		double Output(double[] inputs);		
		void BeginEpoch();
		void EndEpoch();
		double Train();
		double Train(double[] inputs, double[] targets);
	}

	#endregion

	/// <summary>
	/// Base class for all feed-forward networks
	/// </summary>
	public abstract class Network: INetwork
	{

		#region fields

		/**************************************************************/
		/*                      Fields
		/**************************************************************/

		protected double m_Error;
		protected double m_Rate;
		protected double m_Momentum;
		protected double[] m_Inputs;
		protected double[] m_Outputs;
		protected double[] m_Targets;
		protected IActivation m_Activation;

		#endregion

		#region properties

		/**************************************************************/
		/*                      Properties
		/**************************************************************/
		
		/// <summary>
		/// Number of input connections
		/// </summary>
		public int InputCount
		{
			get
			{
				return m_Inputs.Length;
			}
		}

		/// <summary>
		/// Input vector
		/// </summary>
		public double[] Inputs
		{
			get
			{
				return m_Inputs;
			}
		}

		/// <summary>
		/// Input getter
		/// </summary>
		public double GetInput(int index)
		{
			return m_Inputs[index];
		}

		/// <summary>
		/// Input setter
		/// </summary>
		public void SetInput(int index, double value)
		{
			m_Inputs[index] = value;
		}

		/// <summary>
		/// Number of output neurons
		/// </summary>
		public int OutputCount
		{
			get
			{
				return m_Outputs.Length;
			}
		}

		/// <summary>
		/// Output vector
		/// </summary>
		public double[] Outputs
		{
			get
			{
				return m_Outputs;
			}
		}

		/// <summary>
		/// Output getter
		/// </summary>
		public double GetOutput(int index)
		{
			return m_Outputs[index];
		}

		/// <summary>
		/// Expected values for output layer
		/// </summary>
		public double[] Targets
		{
			get
			{
				return m_Targets;
			}
		}

		/// <summary>
		/// Target getter
		/// </summary>
		public double GetTarget(int index)
		{
			return m_Targets[index];
		}

		/// <summary>
		/// Target setter
		/// </summary>
		public void SetTarget(int index, double value)
		{
			m_Targets[index] = value;
		}

		/// <summary>
		/// Index to the best neuron
		/// </summary>
		public int GetBest()
		{
            double max = 0;
			int best = 0;
			for (int i = 0; i < m_Outputs.Length; i++)
			{
				if (m_Outputs[i] > max)
				{
                    max = m_Outputs[i];
					best = i;
				}
			}

			return best;
		}

		/// <summary>
		/// Current activaion function
		/// </summary>
		public virtual IActivation Activation
		{
			get
			{
				return m_Activation;
			}

			set
			{
				m_Activation = value;
			}
		}

		/// <summary>
		/// Network error
		/// </summary>
		public double Error
		{
			get
			{
				return m_Error;
			}
		}
		
		/// <summary>
		/// Learning rate constant
		/// </summary>
		public double LearningRate
		{
			get
			{
				return m_Rate;
			}

			set
			{
				m_Rate = value;
			}
		}

		/// <summary>
		/// Momentum constant
		/// </summary>
		public double Momentum
		{
			get
			{
				return m_Momentum;
			}

			set
			{
				m_Momentum = value;
			}
		}

		#endregion

		#region constructors

		/**************************************************************/
		/*                      Constructors
		/**************************************************************/
		
		public Network(int inputs, int outputs): base()
		{
			if ((inputs <= 0) || (outputs <= 0))
				throw new ArgumentException("Invalid network parameters");

			this.m_Rate = 0.1;
			this.m_Momentum = 0.9;
			this.m_Activation = new Sigmoid();
			this.m_Inputs = new double[inputs];
			this.m_Outputs = new double[outputs];
			this.m_Targets = new double[outputs];
		}

		#endregion

		#region abstracts

		/**************************************************************/
		/*                      Abstracts
		/**************************************************************/

		/// <summary>
		/// Randomizes all weights in the network
		/// </summary>
		public abstract void Randomize();

		/// <summary>
		/// Propagates inputs
		/// </summary>
		public abstract void Run();

		/// <summary>
		/// Prepares network before training one epoch
		/// </summary>
		public abstract void BeginEpoch();

		/// <summary>
		/// End training current epoch
		/// </summary>
		public abstract void EndEpoch();

		/// <summary>
		/// Trains the network (One epoch)
		/// </summary>
		public abstract double Train();

		#endregion

		#region methods

		/**************************************************************/
		/*                      Methods
		/**************************************************************/

		/// <summary>
		/// Propagates inputs
		/// </summary>
		public void Run(double[] inputs)
		{
			/* Copy input array */
			if (inputs.Length != m_Inputs.Length)
				throw new ArgumentException("Invalid input vector");
			inputs.CopyTo(m_Inputs, 0);

			Run();
		}

		/// <summary>
		/// Propagates inputs and returns output of one neuron
		/// </summary>
		/// <param name="index">Index of desired output neuron</param>
		public double Output(int index)
		{
			Run();
			return m_Outputs[index];
		}
		
		/// <summary>
		/// Propagates inputs. Returns output of the first neuron 
		/// </summary>
		public double Output()
		{
			Run();
			return m_Outputs[0];
		}

		/// <summary>
		/// Propagates inputs. Returns output of the first neuron 
		/// </summary>
		public double Output(double[] inputs, int index)
		{			
			Run(inputs);			
			return m_Outputs[index];
		}

		/// <summary>
		/// Propagates inputs. Returns output of the first neuron 
		/// </summary>
		public double Output(double[] inputs)
		{
			return Output(inputs, 0);
		}

		/// <summary>
		/// Trains the network (One epoch)
		/// </summary>
		public double Train(double[] inputs, double[] targets)
		{
			/* Copy input array */
			if (inputs.Length != m_Inputs.Length)
				throw new ArgumentException("Invalid input vector");
			
			inputs.CopyTo(m_Inputs, 0);

			/* Copy target array */
			if (targets.Length != m_Targets.Length)
				throw new ArgumentException("Invalid target vector");
			
			targets.CopyTo(m_Targets, 0);
			
			Run();

			return Train();
		}

		#endregion

	}
}
